﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Sern.Abstracts.Common;
using Sern.Abstracts.Tracker.DataContracts;
using Sern.Abstracts.Tracker.ServiceContracts;
using Sern.Common;
using Sern.ServiceImplementation.TrackerDefinition.Data;
using Microsoft.Practices.Unity;

namespace Sern.ServiceImplementation.TrackerDefinition
{
    public static class TrackerCommon
    {
        /// <summary>
        /// Get the last valid activity time.
        /// </summary>
        public static DateTime LastValidActivityTime
        {
            get
            {
                var lastActivityCutOffPoint = DateTime.UtcNow -
                                              TimeSpan.FromTicks(
                                                  (Int64)
                                                  (Constant.CutOffPointKeepAliveRateMultiplier *
                                                   Constant.ClientKeepAliveRate.Ticks));
                return lastActivityCutOffPoint;
            }
        }

        internal static void Subscribe(Guid jobId, Guid clientId, ClientType clientType, string clientUri, string keyShape = null, string valueShape = null, string clientHostUri = null)
        {
            var clientRepository = ComponentLocator.Unity.Resolve<IRepository<Client>>();

            var newClient = new ClientStatusInfo
            {
                Id = clientId,
                JobId = jobId,
                KeyShape = keyShape,
                ValueShape = valueShape,
                ClientHostUri = clientHostUri,
                Uri = clientUri,
                Type = (int)clientType,
                StartOfSubscription = DateTime.UtcNow,
                EndOfSubscription = null,
                LastAlivePing = null
            };

            clientRepository.Add(newClient);

            clientRepository.Save();

            if (ComponentLocator.Unity.IsRegistered<IEnumerable<IClient<ITrackerMonitoringClient>>>())
            {
                Parallel.ForEach(
                    ComponentLocator.Unity.Resolve<IEnumerable<IClient<ITrackerMonitoringClient>>>(),
                    _ =>
                    {
                        _.Proxy.Notify(
                            new ClientJoinedNotice
                                {
                                    NewClient = new ClientInfo
                                        {
                                            JobId = newClient.JobId,
                                            Id = newClient.Id,
                                            KeyShape = newClient.KeyShape,
                                            Type = (ClientType)newClient.Type,
                                            ValueShape = newClient.ValueShape,
                                            Uri = newClient.Uri,
                                            HostUri = newClient.ClientHostUri
                                        }
                                }
                            );
                        _.Dispose();
                    }
            );
            }
        }

        internal static IEnumerable<JobDescription> GetJobsAboutToEnd()
        {
            var jobs = (from job in ComponentLocator.Unity.Resolve<IRepository<Job>>().Records.OfType<JobDescription>()
                        join client in ComponentLocator.Unity.Resolve<IRepository<Client>>().Records
                            on job.Id equals client.JobId
                        where job.EndingTime != null && job.EndTime == null
                              &&
                              ComponentLocator.Unity.Resolve<IRepository<KeyAtClientLog>>().Records.Where(
                                  k =>

                                           k.ClientId == client.Id && k.State == (int)ProcessingStage.Initiated
                                      ).Count()
                              - ComponentLocator.Unity.Resolve<IRepository<Client>>().Records.Where(_ => _.JobId == job.Id)
                                            .GroupJoin(
                                                ComponentLocator.Unity.Resolve<IRepository<KeyAtClientLog>>().Records,
                                                client2 => client2.Id,
                                                keyAtClientLog => keyAtClientLog.ClientId,
                                                (client2, key) => key
                                            )
                                            .SelectMany(_ => _.DefaultIfEmpty())
                            //.Join(ComponentLocator.Unity.Resolve<IRepository<KeyTree>>().Records, k => k.KeyId, k => k.Id, (left, right) => new { KeyLog = left, right.RedundancyKey})
                            .GroupBy(k => new { k.KeyId, k.RedundancyKey }).Where(
                                gk => gk.Any(_ => _.State == (int)ProcessingStage.Completed) ||
                                      DateTime.UtcNow.Millisecond - gk.Max(_ => _.TimeStamp.Millisecond)
                                      > Constant.ProcessingMaxTimeout.TotalMilliseconds).Count() == 0
                        select new
                            {
                                job
                                #region For Debugging, uncomment.
                                //    ,
                            //    countInitiated =
                            //ComponentLocator.Unity.Resolve<IRepository<KeyAtClientLog>>().Records.Where(
                            //    k => k.ClientId == client.Id && k.State == (int)ProcessingStage.Initiated).Count()
                            //    ,
                            //    countCompleted =
                            //                ComponentLocator.Unity.Resolve<IRepository<Client>>().Records.Where(_ => _.JobId == job.Id)
                            //                .GroupJoin(
                            //                    ComponentLocator.Unity.Resolve<IRepository<KeyAtClientLog>>().Records,
                            //                    client2 => client2.Id,
                            //                    keyAtClientLog => keyAtClientLog.ClientId,
                            //                    (client2, key) => key
                            //                )
                            //                .SelectMany(_ => _.DefaultIfEmpty())
                            //        //.Join(ComponentLocator.Unity.Resolve<IRepository<KeyTree>>().Records, k => k.KeyId, k => k.Id, (left, right) => new { KeyLog = left, right.RedundancyKey})
                            //.GroupBy(k => new { k.KeyId, k.RedundancyKey }).Where(
                            //    gk => gk.Any(_ => _.State == (int)ProcessingStage.Completed) ||
                            //          DateTime.UtcNow.Millisecond - gk.Max(_ => _.TimeStamp.Millisecond)
                                //          > Constant.ProcessingMaxTimeout.TotalMilliseconds).Count()
                                #endregion
                            }).Distinct().ToArray();
            var debug =
                ComponentLocator.Unity.Resolve<IRepository<KeyAtClientLog>>().Records.Count(
                    k => k.State == (int)ProcessingStage.Initiated);
            return jobs.Select(_ => _.job);
        }

        internal static void Unsubscribe(Guid clientId)
        {
            var clientRepository = ComponentLocator.Unity.Resolve<IRepository<Client>>();

            var existingClientRecord = (from client in clientRepository.Records.OfType<ClientStatusInfo>()
                                        where client.Id == clientId
                                              && client.EndOfSubscription == null
                                        select client).Single();

            existingClientRecord.EndOfSubscription = DateTime.UtcNow;

            clientRepository.Save();

            if (ComponentLocator.Unity.IsRegistered<IEnumerable<IClient<ITrackerMonitoringClient>>>())
            {
                Parallel.ForEach(
                    ComponentLocator.Unity.Resolve<IEnumerable<IClient<ITrackerMonitoringClient>>>(),
                    _ =>
                    {
                        _.Proxy.Notify(
                            new ClientLeftNotice()
                                {
                                    OldClient = new ClientInfo
                                        {
                                            JobId = existingClientRecord.JobId,
                                            Id = existingClientRecord.Id,
                                            KeyShape = existingClientRecord.KeyShape,
                                            Type = (ClientType)existingClientRecord.Type,
                                            ValueShape = existingClientRecord.ValueShape,
                                            Uri = existingClientRecord.Uri,
                                            HostUri = existingClientRecord.ClientHostUri

                                        }
                                }
                            );
                        _.Dispose();
                    }
                    );
            }
        }
    }
}
